/*
    This file is part of XCOM Mod Manager.

    XCOM Mod Manager is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    XCOM Mod Manager is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with XCOM Mod Manager.  If not, see <http://www.gnu.org/licenses/>.
*/
/*
 * @author Anthony Surma
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

public class ModMaker 
{
    final static String MAINCONFIG   = "config\\config.xml";            
    final static String MODCONFIGFILEEXTENSION    = "Config";          
    final static String UNPACKEDTAG  = "Unpacked";
    final static String MODFILETAG   = "Original";    
    final static String MODAUTHORTAG = "ModAuthor";
    final static String MODDESCTAG   = "ModDescription";    
    
    class ModDiff
    {
        int offset;
        String data;
        
        ModDiff(int offset, String data)
        {
            this.offset = offset;
            this.data = data;
        }                                                         
    }
    
    class ModData
    {
        String modName;
        String UPKFilename;
        int    dataSum;
        String searchHash;
        LinkedList<ModDiff> diffs;
        
        ModData(String modName, String UPKFile, int dataSum, String originalHash, LinkedList<ModDiff> diffs)                
        {
            this.modName     = modName;
            this.UPKFilename = UPKFile;            
            this.searchHash  = originalHash;
            this.diffs       = diffs;         
            this.dataSum     = dataSum;
        }
        
        String getDiffString()
        {
            StringBuilder result = new StringBuilder();            
            for (ModDiff diff : diffs) result.append(diff.offset).append("/").append(diff.data).append(";");             
            result.setLength(result.length() - 1);   
            
            return result.toString();            
        }                
    }
              
    public static void main(String[] args) throws IOException, SAXException, ParserConfigurationException, NoSuchAlgorithmException, TransformerConfigurationException, TransformerException
    {	
        final MessageDigest md = MessageDigest.getInstance("SHA"); 
        String modConfigFilePath;
        String unpackedFolder;       
        String[] list;        
        File f;
        int cur;       
        ModData[] modData;
        ModMaker mm = new ModMaker();        
        ModXMLFile modXMLFile = new ModXMLFile();

        if (args.length != 1) exitError(1);
        modConfigFilePath = args[0] + "\\" + args[0] + MODCONFIGFILEEXTENSION;
        f = new File(modConfigFilePath);
        if (!f.exists()) exitError(2);
        
        f = new File(MAINCONFIG);
        if (!f.exists()) exitError(3);
                
        list = searchXML(UNPACKEDTAG, MAINCONFIG);
        if (list.length != 1) exitError(4);
        unpackedFolder = list[0];
        
        if (!new File(unpackedFolder).isDirectory()) exitError(5);
        
        f = new File(modConfigFilePath);
        if (!f.exists()) exitError(6);        
        
        list = searchXML(MODFILETAG, modConfigFilePath);                
        if (list.length != 1) exitError(7);                
       
        modData = new ModData[list.length];  
                  
        for (int i = 0; i < list.length; ++i) 
        {               
            modData[i] = mm.getModData(args[0], getUPKFilename(list[i]), getDataSum(unpackedFolder + list[i]), getHash(unpackedFolder + list[i], md), unpackedFolder + list[i]);
        }                        
                                                                          
        String modAuthor = searchXML(MODAUTHORTAG, modConfigFilePath)[0];
        String modDesc   = searchXML(MODDESCTAG, modConfigFilePath)[0];       
        
        modXMLFile.doStuff(modData, modAuthor, modDesc); 
    }    
            
    static String getUPKFilename(String filepath)
    {
        String[] tokens = filepath.split("\\\\");
                
        return tokens[1] + ".upk";
    }
    
    ModData getModData(String modName, String UPKFilename, int dataSum, String hash, String originalFilepath) throws IOException
    {
        return new ModData(modName, UPKFilename, dataSum, hash, getDiff(originalFilepath, modName));
    }
    
    static int getDataSum(String filepath) throws FileNotFoundException, IOException
    {              
        byte[] dataBytes    = new byte[(int)new File(filepath).length()];  
        FileInputStream fis = new FileInputStream(filepath);                             
        int sum = 0;

        fis.read(dataBytes); 
       
        for (int i = dataBytes.length - 1; i >= 0; --i) sum += dataBytes[i];

        fis.close();
        
        return sum;
    }
    
    LinkedList<ModDiff> getDiff(String originalFilepath, String modifiedFilepath) throws FileNotFoundException, IOException
    {
        LinkedList<ModDiff> result = new LinkedList<>();
        FileInputStream modified;  
        FileInputStream original;                
        int offset = 0;
        int data;        
        File f;
        
        f = new File(modifiedFilepath + "\\" + getFilename(originalFilepath));       
        if (!f.exists()) exitError(8);        
        modified = new FileInputStream(f);     
               
        f = new File(originalFilepath);         
        if (!f.exists()) exitError(9);
        original = new FileInputStream(f); 
        
        while ((data = modified.read()) >= 0)
        {
            if (data != original.read())
            {
                String hexString = Integer.toHexString(data);
                if (hexString.length() == 1) hexString = "0" + hexString;
                result.add(new ModDiff(offset, hexString));
            }   
            ++offset;
        }
        
        return result;
    }
    
    static String getFilename(String filepath)
    {
        String[] tokens = filepath.split("\\\\");
        
        return tokens[tokens.length - 1];        
    }
    
    static String getHash(String filepath, MessageDigest md) throws NoSuchAlgorithmException, IOException
    {       
        byte[] dataBytes    = new byte[8192];  
        FileInputStream fis = new FileInputStream(filepath);     
        int totalRead       = 0;
        int numRead; 
        byte[] result;
        
        while ((numRead = fis.read(dataBytes)) > 0) 
        {
            totalRead += numRead;
            md.update(dataBytes, 0, numRead);            
        }                                              
            
        fis.close();
        result = md.digest();  
        md.reset();          
        
        return totalRead + "#" + hashToString(result);
    }
            
    static String hashToString(byte[] hash)
    {
        StringBuilder result = new StringBuilder(20);
      
        for (byte b : hash) 
        {                             
            String hexString = Integer.toHexString((int)b & 0xff);           
            if (hexString.length() == 1) result.append("0");                                                       
            result.append(hexString);          
        }                   
        
        return result.toString();
    }
    
    static String[] searchXML(String searchString, String filename) throws SAXException, IOException
    {       
        XMLReader reader = XMLReaderFactory.createXMLReader();        
        DocHandler handler = new DocHandler(searchString);
                
        reader.setContentHandler(handler);
        reader.parse(filename);
        
        return handler.getResults();
    }
            
    static void exitError(int errorCode)
    {
        System.out.println("ErrorCode " + errorCode);
        System.exit(errorCode);
    }       
}